package io.gitee.zhangbinhub.admin.oauth.authentication

import io.gitee.zhangbinhub.acp.boot.interfaces.LogAdapter
import io.gitee.zhangbinhub.acp.cloud.resource.server.constant.AcpCloudResourceServerConstant
import io.gitee.zhangbinhub.admin.oauth.component.AuthPasswordEncrypt
import io.gitee.zhangbinhub.admin.oauth.component.AuthTokenService
import io.gitee.zhangbinhub.admin.oauth.component.AuthUserService
import io.gitee.zhangbinhub.admin.oauth.constant.OauthConstant
import io.gitee.zhangbinhub.admin.oauth.entity.User
import io.gitee.zhangbinhub.admin.resource.server.tools.TokenTools
import io.gitee.zhangbinhub.admin.resource.server.vo.TokenUserInfoVo
import org.springframework.security.authentication.AuthenticationProvider
import org.springframework.security.core.Authentication
import org.springframework.security.core.AuthenticationException
import org.springframework.security.oauth2.core.*
import org.springframework.security.oauth2.server.authorization.OAuth2Authorization
import org.springframework.security.oauth2.server.authorization.OAuth2TokenType
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2AccessTokenAuthenticationToken
import org.springframework.security.oauth2.server.authorization.authentication.OAuth2ClientAuthenticationToken
import org.springframework.security.oauth2.server.authorization.context.AuthorizationServerContextHolder
import org.springframework.security.oauth2.server.authorization.token.DefaultOAuth2TokenContext
import org.springframework.security.oauth2.server.authorization.token.OAuth2TokenGenerator
import java.security.Principal

class OauthUserPasswordAuthenticationProvider(
    private val logAdapter: LogAdapter,
    private val tokenTools: TokenTools,
    private val authUserService: AuthUserService,
    private val authPasswordEncrypt: AuthPasswordEncrypt,
    private val tokenGenerator: OAuth2TokenGenerator<out OAuth2Token?>,
    private val authTokenService: AuthTokenService
) : AuthenticationProvider {
    @Throws(AuthenticationException::class)
    override fun authenticate(authentication: Authentication): Authentication {
        val oauthUserPasswordAuthenticationRequest = authentication as OauthUserPasswordAuthenticationRequest
        val user = validateUser(oauthUserPasswordAuthenticationRequest)
        val clientPrincipal: OAuth2ClientAuthenticationToken =
            oauthUserPasswordAuthenticationRequest.clientPrincipal as OAuth2ClientAuthenticationToken
        val registeredClient = clientPrincipal.registeredClient
        if (registeredClient == null) {
            val error = OAuth2Error(
                OAuth2ErrorCodes.UNAUTHORIZED_CLIENT, "The client unauthorized.", null
            )
            throw OAuth2AuthenticationException(error)
        }
        // 强制删除之前的token
        authTokenService.removeTokensByClientIdAndUserName(registeredClient.id, user.loginNo)
        val authorizationBuilder: OAuth2Authorization.Builder =
            OAuth2Authorization.withRegisteredClient(registeredClient)
                .authorizationGrantType(AuthorizationGrantType(OauthConstant.granterUserPassword))
                .attributes { attrs -> attrs[Principal::class.java.name] = clientPrincipal }
                .authorizedScopes(registeredClient.scopes)
                .principalName(user.loginNo)
        val tokenContextBuilder: DefaultOAuth2TokenContext.Builder =
            DefaultOAuth2TokenContext.builder().registeredClient(registeredClient)
                .principal(oauthUserPasswordAuthenticationRequest)
                .authorizationServerContext(AuthorizationServerContextHolder.getContext())
                .authorizedScopes(registeredClient.scopes)
                .authorizationGrantType(AuthorizationGrantType(OauthConstant.granterUserPassword))
                .authorizationGrant(oauthUserPasswordAuthenticationRequest)
        var authenticated = false
        // ----- Access token -----
        val tokenContext = tokenContextBuilder.tokenType(OAuth2TokenType.ACCESS_TOKEN).build()
        val generatedAccessToken: OAuth2Token? = tokenGenerator.generate(tokenContext)
        if (generatedAccessToken == null) {
            val error = OAuth2Error(
                OAuth2ErrorCodes.SERVER_ERROR, "The token generator failed to generate the access token.", null
            )
            throw OAuth2AuthenticationException(error)
        }
        val accessToken = OAuth2AccessToken(
            OAuth2AccessToken.TokenType.BEARER,
            generatedAccessToken.tokenValue,
            generatedAccessToken.issuedAt,
            generatedAccessToken.expiresAt,
            tokenContext.authorizedScopes
        )
        if (generatedAccessToken is ClaimAccessor) {
            val claims = (generatedAccessToken as ClaimAccessor).claims.toMutableMap()
            val userAuthorities = authUserService.loadUserAuthorities(user)
            claims[AcpCloudResourceServerConstant.tokenClaimsAuthorities] =
                userAuthorities.map { item -> item.authority }.toSet()
            // 添加附加的用户信息 start
            val userInfo = TokenUserInfoVo()
            userInfo.appId = registeredClient.clientId
            userInfo.id = user.id
            userInfo.loginNo = user.loginNo
            userInfo.name = user.name
            userInfo.mobile = user.mobile
            userInfo.loginTime = System.currentTimeMillis()
            claims[AcpCloudResourceServerConstant.tokenClaimsUserinfo] = tokenTools.encryptUserInfo(userInfo)
            // 添加附加的用户信息 end
            authorizationBuilder.token(accessToken) { metadata ->
                metadata[OAuth2Authorization.Token.CLAIMS_METADATA_NAME] = claims
            }
            if (userAuthorities.isNotEmpty() || accessToken.scopes.isNotEmpty()) {
                authenticated = true
            }
        } else {
            authorizationBuilder.accessToken(accessToken)
        }
        val oAuth2Authorization = authorizationBuilder.build()
        authTokenService.save(oAuth2Authorization)
        return OAuth2AccessTokenAuthenticationToken(
            registeredClient,
            clientPrincipal,
            accessToken,
            null,
            oauthUserPasswordAuthenticationRequest.additionalParameters
        ).apply {
            this.isAuthenticated = authenticated
        }
    }

    override fun supports(authentication: Class<*>): Boolean {
        return OauthUserPasswordAuthenticationRequest::class.java.isAssignableFrom(authentication)
    }

    @Throws(OAuth2AuthenticationException::class)
    fun validateUser(oauthUserPasswordAuthenticationRequest: OauthUserPasswordAuthenticationRequest): User =
        (oauthUserPasswordAuthenticationRequest.principal as? String)?.let { username ->
            authUserService.loadUserByUsername(username).apply {
                if (!this.enabled) {
                    logAdapter.error("用户【${this.loginNo}】已被锁定或禁用！")
                    throw OAuth2AuthenticationException(
                        OAuth2Error(
                            OAuth2ErrorCodes.INVALID_REQUEST, "用户【${this.loginNo}】已被锁定或禁用！", null
                        )
                    )
                }
                (oauthUserPasswordAuthenticationRequest.credentials as? String)?.let { password ->
                    if (!authPasswordEncrypt.matches(password, this.password)) {
                        authUserService.storePasswordErrorTime(username)
                        logAdapter.error("【${username}】用户名或密码不正确！")
                        throw OAuth2AuthenticationException(
                            OAuth2Error(
                                OAuth2ErrorCodes.INVALID_REQUEST, "【${username}】用户名或密码不正确！", null
                            )
                        )
                    } else {
                        authUserService.clearPasswordErrorTime(username)
                    }
                } ?: throw OAuth2AuthenticationException(
                    OAuth2Error(
                        OAuth2ErrorCodes.INVALID_REQUEST, "密码不能为空！", null
                    )
                )
            }
        } ?: throw OAuth2AuthenticationException(
            OAuth2Error(
                OAuth2ErrorCodes.INVALID_REQUEST, "用户名不能为空！", null
            )
        )
}